package top.lc951.myglide.util.pool;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.util.Pools.Pool;
import androidx.core.util.Pools.SimplePool;
import androidx.core.util.Pools.SynchronizedPool;
import java.util.ArrayList;
import java.util.List;

public class FactoryPools {
    private static final String TAG = "FactoryPools";
    private static final int DEFAULT_POOL_SIZE = 20;
    private static final Resetter<Object> EMPTY_RESETTER =
            new Resetter<Object>() {
                @Override
                public void reset(@NonNull Object object) {
                    // Do nothing.
                }
            };

    private FactoryPools() {}

    /**
     * Returns a non-thread safe {@link Pools.Pool} that never returns {@code null} from {@link
     * Pools.Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
     * the given maximum size.
     *
     * <p>If the pool is empty when {@link Pools.Pool#acquire()} is called, the given {@link Factory} will
     * be used to create a new instance.
     *
     * @param <T> The type of object the pool will contains.
     */
    @NonNull
    public static <T extends Poolable> Pool<T> simple(int size, @NonNull Factory<T> factory) {
        return build(new SimplePool<T>(size), factory);
    }

    /**
     * Returns a new thread safe {@link Pool} that never returns {@code null} from {@link
     * Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
     * the given maximum size.
     *
     * <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
     * be used to create a new instance.
     *
     * @param <T> The type of object the pool will contains.
     */
    @NonNull
    public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {
        return build(new SynchronizedPool<T>(size), factory);
    }

    @NonNull
    private static <T extends Poolable> Pool<T> build(
            @NonNull Pool<T> pool, @NonNull Factory<T> factory) {
        return build(pool, factory, FactoryPools.<T>emptyResetter());
    }

    @NonNull
    private static <T> Pool<T> build(
            @NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
        return new FactoryPool<>(pool, factory, resetter);
    }

    @NonNull
    @SuppressWarnings("unchecked")
    private static <T> Resetter<T> emptyResetter() {
        return (Resetter<T>) EMPTY_RESETTER;
    }

    /**
     * Creates new instances of the given type.
     *
     * @param <T> The type of Object that will be created.
     */
    public interface Factory<T> {
        T create();
    }

    /**
     * Resets state when objects are returned to the pool.
     *
     * @param <T> The type of Object that will be reset.
     */
    public interface Resetter<T> {
        void reset(@NonNull T object);
    }

    /**
     * Allows additional verification to catch errors caused by using objects while they are in an
     * object pool.
     */
    public interface Poolable {
        @NonNull
        StateVerifier getVerifier();
    }

    private static final class FactoryPool<T> implements Pool<T> {
        private final Factory<T> factory;
        private final Resetter<T> resetter;
        private final Pool<T> pool;

        FactoryPool(@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
            this.pool = pool;
            this.factory = factory;
            this.resetter = resetter;
        }

        @Override
        public T acquire() {
            T result = pool.acquire();
            if (result == null) {
                result = factory.create();
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Created new " + result.getClass());
                }
            }
            if (result instanceof Poolable) {
                ((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);
            }
            return result;
        }

        @Override
        public boolean release(@NonNull T instance) {
            if (instance instanceof Poolable) {
                ((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);
            }
            resetter.reset(instance);
            return pool.release(instance);
        }
    }
}
